
//Debug
#include <iostream>

//Qt includes
#include <QFile>
#include <QtTest/QtTest>


//Our includes
#include "cwDatabaseTest.h"
#include <plDatabase>
#include "TestA.h"
#include <plFactory>
#include "TestB.h"
#include "TestC.h"
#include "TestD.h"
#include "TestF.h"

const QString cwDatabaseTest::TestDatabase = "test.cw";

//Create the database
void cwDatabaseTest::init() {
    TestDB = new plDatabase();
    TestDB->Open(TestDatabase.toStdString());

    //Sets the database for all cwDataObjects
    //cwObject::SetDatabase(TestDB);
}

//Deletes the database off of disk
void cwDatabaseTest::cleanup() {
    delete TestDB;

    QString dbName;
    QTextStream(&dbName) << TestName << ".cw";
    QFile::remove(dbName);
    QFile::copy(TestDatabase, dbName);
    QFile::remove(TestDatabase);
}

//Test the object factory to make sure it's working
void cwDatabaseTest::factoryTest() {
    TestName = "factoryTest";

    plFactory* factory = new plFactory();
    TestA* obj1 = (TestA*)factory->New("TestA");
    QVERIFY(obj1 != NULL);
    obj1->SetTestString("Awesome");

    TestB* obj2 = (TestB*)factory->New("TestB");
    QVERIFY(obj2 != NULL);

    TestC* obj3 = (TestC*)factory->New("TestC");
    QVERIFY(obj3 != NULL);



}

void cwDatabaseTest::simpleAddTest() {
    TestName = "simpleAddGetTest";

    TestA* obj = new TestA();

    TestA* outObj = TestDB->GetObject<TestA>(1);
    QCOMPARE(outObj, obj);
}


void cwDatabaseTest::simpleSetTest_data() {
    QTest::addColumn<int>("pointerId");
    QTest::addColumn<int>("intData");
    QTest::addColumn<double>("doubleData");
    QTest::addColumn<QString>("stringData");
    QTest::addColumn<long long>("longlongData");

    long long longData1 = 1;
    long long longData2 = 23109939;
    long long longData3 = -29373487;
    long long longData4 = 0;
    long long longData5 = 12093092;

    QTest::newRow("data1") << 1 << 1 << 1.0 << "One" << longData1;
    QTest::newRow("data2") << 1 << 1234568792 << 2309394.23 << "Large" << longData2;
    QTest::newRow("data3") << 1 << -1283948 << 0.231382 << "Small" << longData3;
    QTest::newRow("data4") << 1 << 0 << 0.0 << "Zero" << longData4;
    QTest::newRow("data5") << 1 << 112019039 << 0.1e-5 << "Blaoeunth!a oenut \" asoetuh ` \' aoe fail!!!" << longData5;


}

void cwDatabaseTest::simpleSetTest() {
    TestName = "simpleSetTest";

    TestA* obj = new TestA();

    QFETCH(int, pointerId);
    QFETCH(int, intData);
    QFETCH(double, doubleData);
    QFETCH(QString, stringData);
    QFETCH(long long, longlongData);

    TestA* outObj = TestDB->GetObject<TestA>(pointerId);
    QCOMPARE(outObj, obj);

    outObj->SetTestInt(intData);
    outObj->SetTestDouble(doubleData);
    outObj->SetTestString(stringData.toStdString());
    outObj->SetTestLong(longlongData);

    QCOMPARE(outObj->GetTestInt(), intData);
    QCOMPARE(outObj->GetTestLong(), longlongData);
    QCOMPARE(outObj->GetTestString(), stringData.toStdString());
    QCOMPARE(outObj->GetTestDouble(), doubleData);
}

void cwDatabaseTest::simpleSetLoadTest_data() {
    simpleSetTest_data();
}


void cwDatabaseTest::simpleSetLoadTest() {
    TestName = "simpleSetLoadTest";

    TestA* obj = new TestA();

    QFETCH(int, pointerId);
    QFETCH(int, intData);
    QFETCH(double, doubleData);
    QFETCH(QString, stringData);
    QFETCH(long long, longlongData);

    TestA* outObj = TestDB->GetObject<TestA>(pointerId);
    QCOMPARE(outObj, obj);

    outObj->SetTestInt(intData);
    outObj->SetTestDouble(doubleData);
    outObj->SetTestString(stringData.toStdString());
    outObj->SetTestLong(longlongData);

    QCOMPARE(outObj->GetTestInt(), intData);
    QCOMPARE(outObj->GetTestLong(), longlongData);
    QCOMPARE(outObj->GetTestString(), stringData.toStdString());
    QCOMPARE(outObj->GetTestDouble(), doubleData);

    delete TestDB;

    QTime dieTime = QTime::currentTime().addMSecs(200);
    while( QTime::currentTime() < dieTime )
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

    TestDB = new plDatabase();
    QCOMPARE(TestDB->Open(TestDatabase.toStdString()), 0);

    //Sets the database for all cwDataObjects
    //plObject::SetDatabase(TestDB);

    outObj = TestDB->GetObject<TestA>(pointerId);

    QVERIFY(outObj);
    QVERIFY(outObj != obj);

    QCOMPARE(outObj->GetTestInt(), intData);
    QCOMPARE(outObj->GetTestLong(), longlongData);
    QCOMPARE(outObj->GetTestString(), stringData.toStdString());
    QCOMPARE(outObj->GetTestDouble(), doubleData);
}


void cwDatabaseTest::multiObjectAddTest() {
    TestName = "multiObjectAddTest";

    TestA* obj1 = new TestA();
    TestB* obj2 = new TestB();
    TestC* obj3 = new TestC();

    TestA* outObj1 = TestDB->GetObject<TestA>(1);
    QCOMPARE(outObj1, obj1);
    TestB* outObj2 = TestDB->GetObject<TestB>(2);
    QCOMPARE(outObj2, obj2);
    TestC* outObj3 = TestDB->GetObject<TestC>(3);
    QCOMPARE(outObj3, obj3);
}


void cwDatabaseTest::multiObjectSetLoadTest() {
    TestName = "multiObjectSetLoadTest";

    TestA* obj1 = new TestA();
    TestB* obj2 = new TestB();
    TestB* obj3 = new TestC();
    TestB* obj4 = new TestC();
    TestB* obj5 = new TestB();
    TestB* obj6 = new TestB();
    TestC* obj7 = new TestC();
    TestA* obj8 = new TestA();

    //Setup TestA pointers
    obj1->SetTestDouble(1.1);
    obj1->SetTestInt(-504);
    obj1->SetTestPointer(obj8);
    QCOMPARE(obj8, obj1->GetTestPointer());
    obj1->SetTestPointer(obj1);
    QCOMPARE(obj1, obj1->GetTestPointer());

    obj8->SetTestLong(1234567890);
    QVERIFY(NULL == obj8->GetTestPointer());
    obj8->SetTestPointer(obj1);
    QCOMPARE(obj1, obj8->GetTestPointer());
    obj8->SetTestPointer(NULL);
    QCOMPARE((TestA*)NULL, obj8->GetTestPointer());



    //Set all the data for the B
    obj2->SetX(2.1);
    obj2->SetY(2.2);
    obj2->SetZ(2.3);
    obj2->SetW(2.4);

    obj3->SetX(3.1);
    obj3->SetY(3.2);
    obj3->SetZ(3.3);
    obj3->SetW(3.4);

    obj4->SetX(4.1);
    obj4->SetY(4.2);
    obj4->SetZ(4.3);
    obj4->SetW(4.4);

    obj5->SetX(5.1);
    obj5->SetY(5.2);
    obj5->SetZ(5.3);
    obj5->SetW(5.4);

    obj6->SetX(6.1);
    obj6->SetY(6.2);
    obj6->SetZ(6.3);
    obj6->SetW(6.4);

    obj7->SetX(8.1);
    obj7->SetY(8.2);
    obj7->SetZ(8.3);
    obj7->SetW(8.4);

    //Type casting check
    TestC* obj3casted = (TestC*)obj3;
    obj3casted->SetCached(true);
    obj3casted->SetName("obj2casted worked");

    TestC* obj4casted = (TestC*)obj4;
    obj4casted->SetName("obj3casted worked");

    //Make tree structure
    obj7->SetLeft(obj5);
    obj7->SetRight(obj3casted);
    obj3casted->SetLeft(obj4);
    obj3casted->SetRight(obj2);
    obj4casted->SetLeft(obj5);

    //Close the database
    delete TestDB;

    QTime dieTime = QTime::currentTime().addMSecs(200);
    while( QTime::currentTime() < dieTime )
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

    TestDB = new plDatabase();
    QCOMPARE(TestDB->Open(TestDatabase.toStdString()), 0);

    //cwDataObject::SetDatabase(TestDB);

    //Start testing gets
    obj1 = TestDB->GetObject<TestA>(1);
    obj2 = TestDB->GetObject<TestB>(2);
    obj3 = TestDB->GetObject<TestB>(3);
    obj4 = TestDB->GetObject<TestB>(4);
    obj5 = TestDB->GetObject<TestB>(5);
    obj6 = TestDB->GetObject<TestB>(6);
    obj7 = TestDB->GetObject<TestC>(7);
    obj8 = TestDB->GetObject<TestA>(8);

    QVERIFY(obj1 != NULL);
    QVERIFY(obj2 != NULL);
    QVERIFY(obj3 != NULL);
    QVERIFY(obj4 != NULL);
    QVERIFY(obj5 != NULL);
    QVERIFY(obj6 != NULL);
    QVERIFY(obj7 != NULL);
    QVERIFY(obj8 != NULL);

    QCOMPARE(obj1->GetTestDouble(), 1.1);
    QCOMPARE(obj1->GetTestInt(), -504);
    QCOMPARE(obj1->GetTestPointer(), obj1);

    QCOMPARE(obj8->GetTestLong(), (long long)1234567890);
    QCOMPARE(obj8->GetTestPointer(), (TestA*)NULL);

    //Check loading on the fly
    QCOMPARE(obj2->GetX(), 2.1);
    QCOMPARE(obj2->GetY(), 2.2);
    QCOMPARE(obj2->GetZ(), 2.3);
    QCOMPARE(obj2->GetW(), 2.4);

    QCOMPARE(obj3->GetX(), 3.1);
    QCOMPARE(obj3->GetY(), 3.2);
    QCOMPARE(obj3->GetZ(), 3.3);
    QCOMPARE(obj3->GetW(), 3.4);

    QCOMPARE(obj4->GetX(), 4.1);
    QCOMPARE(obj4->GetY(), 4.2);
    QCOMPARE(obj4->GetZ(), 4.3);
    QCOMPARE(obj4->GetW(), 4.4);

    QCOMPARE(obj5->GetX(), 5.1);
    QCOMPARE(obj5->GetY(), 5.2);
    QCOMPARE(obj5->GetZ(), 5.3);
    QCOMPARE(obj5->GetW(), 5.4);

    QCOMPARE(obj6->GetX(), 6.1);
    QCOMPARE(obj6->GetY(), 6.2);
    QCOMPARE(obj6->GetZ(), 6.3);
    QCOMPARE(obj6->GetW(), 6.4);

    QCOMPARE(obj7->GetX(), 8.1);
    QCOMPARE(obj7->GetY(), 8.2);
    QCOMPARE(obj7->GetZ(), 8.3);
    QCOMPARE(obj7->GetW(), 8.4);

    //Check caching
    QCOMPARE(obj2->GetX(), 2.1);
    QCOMPARE(obj2->GetY(), 2.2);
    QCOMPARE(obj2->GetZ(), 2.3);
    QCOMPARE(obj2->GetW(), 2.4);

    QCOMPARE(obj3->GetX(), 3.1);
    QCOMPARE(obj3->GetY(), 3.2);
    QCOMPARE(obj3->GetZ(), 3.3);
    QCOMPARE(obj3->GetW(), 3.4);

    QCOMPARE(obj4->GetX(), 4.1);
    QCOMPARE(obj4->GetY(), 4.2);
    QCOMPARE(obj4->GetZ(), 4.3);
    QCOMPARE(obj4->GetW(), 4.4);

    QCOMPARE(obj5->GetX(), 5.1);
    QCOMPARE(obj5->GetY(), 5.2);
    QCOMPARE(obj5->GetZ(), 5.3);
    QCOMPARE(obj5->GetW(), 5.4);

    QCOMPARE(obj6->GetX(), 6.1);
    QCOMPARE(obj6->GetY(), 6.2);
    QCOMPARE(obj6->GetZ(), 6.3);
    QCOMPARE(obj6->GetW(), 6.4);

    QCOMPARE(obj7->GetX(), 8.1);
    QCOMPARE(obj7->GetY(), 8.2);
    QCOMPARE(obj7->GetZ(), 8.3);
    QCOMPARE(obj7->GetW(), 8.4);


    //Type casting check
    TestC* cast1 = dynamic_cast<TestC*>(obj3);
    QVERIFY(cast1 != NULL);

    TestC* cast2 = dynamic_cast<TestC*>(obj4);
    QVERIFY(cast2 != NULL);

    //Test tree structure
    QCOMPARE(obj7->GetLeft(), obj5);
    QCOMPARE(obj7->GetRight(), cast1);
    QCOMPARE(cast1->GetLeft(), obj4);
    QCOMPARE(cast1->GetRight(), obj2);
    QCOMPARE(cast2->GetLeft(), obj5);
}


void cwDatabaseTest::multiDeleteTest() {
    TestName = "multiDeleteTest";

    TestA* obj1 = new TestA();
    TestB* obj2 = new TestB();
    TestB* obj3 = new TestC();
    TestB* obj4 = new TestC();
    TestB* obj5 = new TestB();
    TestB* obj6 = new TestB();
    TestC* obj7 = new TestC();
    TestA* obj8 = new TestA();

    //Setup TestA pointers
    obj1->SetTestDouble(1.1);
    obj1->SetTestInt(-504);
    obj1->SetTestPointer(obj8);
    QCOMPARE(obj8, obj1->GetTestPointer());
    obj1->SetTestPointer(obj1);
    QCOMPARE(obj1, obj1->GetTestPointer());

    obj8->SetTestLong(1234567890);
    QVERIFY(NULL == obj8->GetTestPointer());
    obj8->SetTestPointer(obj1);
    QCOMPARE(obj1, obj8->GetTestPointer());


    //Set all the data for the B
    obj2->SetX(2.1);
    obj2->SetY(2.2);
    obj2->SetZ(2.3);
    obj2->SetW(2.4);

    obj3->SetX(3.1);
    obj3->SetY(3.2);
    obj3->SetZ(3.3);
    obj3->SetW(3.4);

    obj4->SetX(4.1);
    obj4->SetY(4.2);
    obj4->SetZ(4.3);
    obj4->SetW(4.4);

    obj5->SetX(5.1);
    obj5->SetY(5.2);
    obj5->SetZ(5.3);
    obj5->SetW(5.4);

    obj6->SetX(6.1);
    obj6->SetY(6.2);
    obj6->SetZ(6.3);
    obj6->SetW(6.4);

    obj7->SetX(8.1);
    obj7->SetY(8.2);
    obj7->SetZ(8.3);
    obj7->SetW(8.4);

    //Type casting check
    TestC* obj3casted = (TestC*)obj3;
    obj3casted->SetCached(true);
    obj3casted->SetName("obj2casted worked");

    TestC* obj4casted = (TestC*)obj4;
    obj4casted->SetName("obj3casted worked");

    //Make tree structure
    obj7->SetLeft(obj5);
    obj7->SetRight(obj3casted);
    obj3casted->SetLeft(obj4);
    obj3casted->SetRight(obj2);
    obj4casted->SetLeft(obj5);

    delete obj1;
    std::vector<TestA*> listA;
    TestDB->GetAllObjects<TestA>(listA);
    QCOMPARE((int)listA.size(), 1);
    listA.clear();

    delete obj2;
    std::vector<TestB*> listB;
    TestDB->GetAllObjects<TestB>(listB);
    QCOMPARE((int)listB.size(), 5);
    listB.clear();

    delete obj3;
    TestDB->GetAllObjects<TestB>(listB);
    QCOMPARE((int)listB.size(), 4);
    listB.clear();

    std::vector<TestC*> listC;
    TestDB->GetAllObjects<TestC>(listC);
    QCOMPARE((int)listC.size(), 2);
    listC.clear();

    delete obj4;
    TestDB->GetAllObjects<TestB>(listB);
    QCOMPARE((int)listB.size(), 3);
    listB.clear();

    TestDB->GetAllObjects<TestC>(listC);
    QCOMPARE((int)listC.size(), 1);
    listC.clear();

    delete obj5;
    TestDB->GetAllObjects<TestB>(listB);
    QCOMPARE((int)listB.size(), 2);
    listB.clear();

    delete obj6;
    TestDB->GetAllObjects<TestB>(listB);
    QCOMPARE((int)listB.size(), 1);
    listB.clear();

    delete obj7;
    TestDB->GetAllObjects<TestB>(listB);
    QCOMPARE((int)listB.size(), 0);
    listB.clear();

    TestDB->GetAllObjects<TestC>(listC);
    QCOMPARE((int)listC.size(), 0);
    listC.clear();

    delete obj8;
    TestDB->GetAllObjects<TestA>(listA);
    QCOMPARE((int)listA.size(), 0);
    listA.clear();

}

void cwDatabaseTest::getAllTest() {

    TestName = "getAllTest";

    QList<TestA*> TestAs;
    QList<TestB*> TestBs;
    QList<TestC*> TestCs;

    TestDB->Begin();

    int total = 300;

    for(int i = 0; i < total; i++) {
        TestA* a = new TestA();
        TestB* b = new TestB();
        TestC* c = new TestC();

        TestAs.push_back(a);
        TestBs.push_back(b);
        TestCs.push_back(c);
    }

    TestDB->End();

    std::vector<TestA*> dbTestAs;
    TestDB->GetAllObjects<TestA>(dbTestAs);
    std::vector<TestB*> dbTestBs;
    TestDB->GetAllObjects<TestB>(dbTestBs);
    std::vector<TestC*> dbTestCs;
    TestDB->GetAllObjects<TestC>(dbTestCs);

    QCOMPARE(TestAs.size(), (int)dbTestAs.size());
    QCOMPARE(TestBs.size() + TestCs.size(), (int)dbTestBs.size());
    QCOMPARE(TestCs.size(), (int)dbTestCs.size());

    for(int i = 0; i < total; i++) {
        //std::cout << "i = " << i << std::endl;
        QCOMPARE(dbTestAs.at(i), TestAs.at(i));
        //	QCOMPARE(dbTestBs.at(2*i), TestBs.at(i));
        //	QCOMPARE(dbTestBs.at(2*(i+1)), TestCs.at(i));
        QCOMPARE(dbTestCs.at(i), TestCs.at(i));
    }
}

void cwDatabaseTest::listTest() {

    TestName = "listTest";

    TestDB->Begin();

    TestD* objd1 = new TestD();
    TestD* objd2 = new TestD();
    TestD* objd3 = new TestD();

    int num = 100;
    TestA** objs = new TestA*[num];
    for(int i = 0; i < num; i++) {
        objs[i] = new TestA();
        objd1->AddA(objs[i]);
        objd2->AddA(objs[i]);
        objd3->AddA(objs[i]);
    }

    for(int i = 0; i < num; i++) {
        objd1->AddDouble((double)i);
        objd2->AddDouble((double)i);
        objd3->AddDouble((double)i);
    }

    TestDB->End();

    //Test the caching
    for(int i = 0; i < num; i++) {
        QCOMPARE(objs[i], objd1->GetA(i));
        QCOMPARE(objs[i], objd2->GetA(i));
        QCOMPARE(objs[i], objd3->GetA(i));
    }

    //Test the caching
    for(int i = 0; i < num; i++) {
        QCOMPARE((double)i, objd1->GetDouble(i));
        QCOMPARE((double)i, objd1->GetDouble(i));
        QCOMPARE((double)i, objd1->GetDouble(i));
    }

    //Test loading from disk
    QTime dieTime = QTime::currentTime().addMSecs(200);
    while( QTime::currentTime() < dieTime )
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

    TestDB = new plDatabase();
    QCOMPARE(TestDB->Open(TestDatabase.toStdString()), 0);

    //cwDataObject::SetDatabase(TestDB);

    std::vector<TestD*> TestDs;
    TestDB->GetAllObjects<TestD>(TestDs);

    //Test all the numbers
    for(unsigned int i = 0; i < TestDs.size(); i++) {
        TestD* current = TestDs.at(i);
        for(int i = 0; i < current->NumDoubles(); i++) {
            QCOMPARE(current->GetDouble(i), (double)i);
        }
    }

    //Make sure the objects aren't null
    for(unsigned int i = 0; i < TestDs.size(); i++) {
        TestD* current = TestDs.at(i);
        for(int i = 0;  i < current->NumAs(); i++) {
            QVERIFY(current->GetA(i) != NULL);
        }
    }

    std::vector<TestA*> TestAs;
    TestDB->GetAllObjects<TestA>(TestAs);

    for(unsigned int i = 0; i < TestDs.size(); i++) {
        TestD* current = TestDs.at(i);

        for(unsigned int j = 0; j < TestAs.size(); j++) {
            QCOMPARE(current->GetA(j), TestAs.at(j));
        }
    }


    for(unsigned int i = 0; i < TestDs.size(); i++) {
        TestD* current = TestDs.at(i);

        for(unsigned int j = 0; j < TestAs.size(); j++) {
            current->ReplaceA(j, NULL);
            QCOMPARE(current->GetA(j), (TestA*)NULL);
        }
    }

}

/*!
  Test creating an new element in the constructor
  */
void cwDatabaseTest::newInConstructorTest() {
    TestName = "newInConstructorTest";

    TestF* obj1 = new TestF();
    TestF* obj2 = new TestF();
    TestF* obj3 = new TestF();
    TestF* obj4 = new TestF();

    obj2->SetRoot(obj1);
    obj3->SetRoot(obj4);

    //Test loading from disk
    QTime dieTime = QTime::currentTime().addMSecs(200);
    while( QTime::currentTime() < dieTime )
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

    TestDB = new plDatabase();
    QCOMPARE(TestDB->Open(TestDatabase.toStdString()), 0);

    std::vector<TestF*> TestFs;
    TestDB->GetAllObjects<TestF>(TestFs);
    obj1 = TestFs[0];
    obj2 = TestFs[1];
    obj3 = TestFs[2];
    obj4 = TestFs[3];

    QCOMPARE(obj2->GetRoot(), obj1);
    QCOMPARE(obj3->GetRoot(), obj4);
}

QTEST_MAIN(cwDatabaseTest)
